// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <variant>
#include <vector>

#include "absl/functional/overload.h"
#include "absl/strings/string_view.h"
#include "quiche/quic/core/qpack/qpack_decoder.h"
#include "quiche/quic/core/qpack/qpack_progressive_decoder.h"
#include "quiche/quic/core/quic_error_codes.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/test_tools/qpack/qpack_test_utils.h"
#include "quiche/common/platform/api/quiche_fuzztest.h"
#include "quiche/common/platform/api/quiche_logging.h"

namespace quic {
namespace test {
namespace {

struct DecoderAndHandler {
  std::unique_ptr<QpackProgressiveDecoder> decoder;
  std::unique_ptr<QpackProgressiveDecoder::HeadersHandlerInterface> handler;
};

using DecoderAndHandlerMap = std::map<QuicStreamId, DecoderAndHandler>;

// Class that sets externally owned |error_detected| to true
// on encoder stream error.
class ErrorDelegate : public QpackDecoder::EncoderStreamErrorDelegate {
 public:
  ErrorDelegate(bool* error_detected) : error_detected_(error_detected) {}
  ~ErrorDelegate() override = default;

  void OnEncoderStreamError(QuicErrorCode /*error_code*/,
                            absl::string_view /*error_message*/) override {
    *error_detected_ = true;
  }

 private:
  bool* const error_detected_;
};

// Class that destroys DecoderAndHandler when decoding completes, and sets
// externally owned |error_detected| to true on encoder stream error.
class HeadersHandler : public QpackProgressiveDecoder::HeadersHandlerInterface {
 public:
  HeadersHandler(QuicStreamId stream_id,
                 DecoderAndHandlerMap* processing_decoders,
                 bool* error_detected)
      : stream_id_(stream_id),
        processing_decoders_(processing_decoders),
        error_detected_(error_detected) {}
  ~HeadersHandler() override = default;

  void OnHeaderDecoded(absl::string_view /*name*/,
                       absl::string_view /*value*/) override {}

  // Remove DecoderAndHandler from |*processing_decoders|.
  void OnDecodingCompleted() override {
    // Will delete |this|.
    size_t result = processing_decoders_->erase(stream_id_);
    QUICHE_CHECK_EQ(1u, result);
  }

  void OnDecodingErrorDetected(QuicErrorCode /*error_code*/,
                               absl::string_view /*error_message*/) override {
    *error_detected_ = true;
  }

 private:
  const QuicStreamId stream_id_;
  DecoderAndHandlerMap* const processing_decoders_;
  bool* const error_detected_;
};

class FuzzerAction {
 public:
  struct Decode {
    std::string encoded_data;
  };
  struct CreateProgressiveDecoder {
    QuicStreamId stream_id;
  };
  struct DecodeWithExistingDecoder {
    size_t distance;
    std::string encoded_data;
  };
  struct EndHeaderBlock {
    size_t distance;
  };

  using ActionVariant = std::variant<Decode, CreateProgressiveDecoder,
                                     DecodeWithExistingDecoder, EndHeaderBlock>;
};

// This fuzzer exercises QpackDecoder.  It should be able to cover all possible
// code paths.  There is no point in encoding QpackDecoder's output to turn this
// into a roundtrip test, because the same header list can be encoded in many
// different ways, so the output could not be expected to match the original
// input.
void DoesNotCrash(uint64_t maximum_dynamic_table_capacity,
                  uint64_t maximum_blocked_streams,
                  const std::vector<FuzzerAction::ActionVariant>& actions) {
  // |error_detected| will be set to true if an error is encountered either in a
  // header block or on the encoder stream.
  bool error_detected = false;

  ErrorDelegate encoder_stream_error_delegate(&error_detected);
  QpackDecoder decoder(maximum_dynamic_table_capacity, maximum_blocked_streams,
                       &encoder_stream_error_delegate);

  NoopQpackStreamSenderDelegate decoder_stream_sender_delegate;
  decoder.set_qpack_stream_sender_delegate(&decoder_stream_sender_delegate);

  // Decoders still reading the header block, with corresponding handlers.
  DecoderAndHandlerMap reading_decoders;

  // Decoders still processing the completely read header block,
  // with corresponding handlers.
  DecoderAndHandlerMap processing_decoders;

  for (const FuzzerAction::ActionVariant& action : actions) {
    if (error_detected) {
      break;
    }
    std::visit(
        absl::Overload{
            [&](const FuzzerAction::Decode& params) {
              // Feed encoder stream data to QpackDecoder.
              decoder.encoder_stream_receiver()->Decode(params.encoded_data);
            },
            [&](const FuzzerAction::CreateProgressiveDecoder& params) {
              static constexpr QuicStreamId kMaxStreamId = 255;

              // Create new progressive decoder.
              QuicStreamId stream_id = std::min(params.stream_id, kMaxStreamId);
              if (reading_decoders.find(stream_id) != reading_decoders.end() ||
                  processing_decoders.find(stream_id) !=
                      processing_decoders.end()) {
                return;
              }

              DecoderAndHandler decoder_and_handler;
              decoder_and_handler.handler = std::make_unique<HeadersHandler>(
                  stream_id, &processing_decoders, &error_detected);
              decoder_and_handler.decoder = decoder.CreateProgressiveDecoder(
                  stream_id, decoder_and_handler.handler.get());
              reading_decoders.insert(
                  {stream_id, std::move(decoder_and_handler)});
            },
            [&](const FuzzerAction::DecodeWithExistingDecoder& params) {
              // Feed header block data to existing decoder.
              if (reading_decoders.empty()) {
                return;
              }

              const size_t distance =
                  std::min(params.distance, reading_decoders.size() - 1);
              auto it = reading_decoders.begin();
              std::advance(it, distance);

              it->second.decoder->Decode(params.encoded_data);
            },
            [&](const FuzzerAction::EndHeaderBlock& params) {
              if (reading_decoders.empty()) {
                return;
              }

              const size_t distance =
                  std::min(params.distance, reading_decoders.size() - 1);
              auto it = reading_decoders.begin();
              std::advance(it, distance);

              QpackProgressiveDecoder* reading_decoder =
                  it->second.decoder.get();

              // Move DecoderAndHandler to |processing_decoders| first, because
              // EndHeaderBlock() might synchronously call
              // OnDecodingCompleted().
              QuicStreamId stream_id = it->first;
              processing_decoders.insert({stream_id, std::move(it->second)});
              reading_decoders.erase(it);

              reading_decoder->EndHeaderBlock();
            }},
        action);
  }
}
FUZZ_TEST(QpackDecoderFuzzer, DoesNotCrash)
    .WithDomains(
        // Maximum 256 byte dynamic table.  Such a small size helps test
        // draining entries and eviction.
        fuzztest::InRange(0u, 256u),
        // Maximum 256 blocked streams.
        fuzztest::InRange(0u, 256u),
        // Maximum 256 data fragments to limit runtime and memory usage.
        fuzztest::VectorOf(fuzztest::Arbitrary<FuzzerAction::ActionVariant>())
            .WithMaxSize(256));

}  // namespace
}  // namespace test
}  // namespace quic
